#include <jni.h>
#ifdef BUILD_TO_ANDROID_OS
#include <JNIHelp.h>
#endif
#include <string>
#include <stdlib.h>
#include <dlfcn.h>

#include <tinyalsa/asoundlib.h>

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <signal.h>
#include <endian.h>
#include <unistd.h>

#ifdef __ANDROID__
#include <android/log.h>
#ifndef TAG
#define TAG "BAHAL_NativeLib"
#endif

#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
#else
#define LOGI(str, ...) do{printf("BAHAL_nativelib ");printf(str,##__VA_ARGS__);printf("\n");}while(0)
#define LOGD(str, ...) do{printf("BAHAL_nativelib ");printf(str,##__VA_ARGS__);printf("\n");}while(0)
#define LOGW(str, ...) do{printf("BAHAL_nativelib ");printf(str,##__VA_ARGS__);printf("\n");}while(0)
#define LOGE(str, ...) do{printf("BAHAL_nativelib ");printf(str,##__VA_ARGS__);printf("\n");}while(0)
#endif

#define ID_RIFF 0x46464952
#define ID_WAVE 0x45564157
#define ID_FMT  0x20746d66
#define ID_DATA 0x61746164

struct riff_wave_header {
    uint32_t riff_id;
    uint32_t riff_sz;
    uint32_t wave_id;
};

struct chunk_header {
    uint32_t id;
    uint32_t sz;
};

struct chunk_fmt {
    uint16_t audio_format;
    uint16_t num_channels;
    uint32_t sample_rate;
    uint32_t byte_rate;
    uint16_t block_align;
    uint16_t bits_per_sample;
};

static  __attribute__((constructor)) int init(){
    LOGD("constructor enter");
    return 0;
}

static __attribute__((destructor)) int deInit(){
    LOGD("destructor enter");
    return 0;
}


static std::string parse(char const * filename) {
    FILE *file;
    struct riff_wave_header riff_wave_header;
    struct chunk_header chunk_header;
    struct chunk_fmt chunk_fmt;
    int more_chunks = 1;
    int offset = 0;
    std::string ret = "";

    file = fopen(filename, "rb");
    if (!file) {
        LOGE("Unable to open file '%s'\n", filename);
        return ret;
    }

    fread(&riff_wave_header, sizeof(riff_wave_header), 1, file);
    offset += sizeof(riff_wave_header);
    if ((riff_wave_header.riff_id != ID_RIFF) ||
        (riff_wave_header.wave_id != ID_WAVE)) {
        LOGI("Error: '%s' is not a riff/wave file\n", filename);
        fclose(file);
        return ret;
    }

    do {
        fread(&chunk_header, sizeof(chunk_header), 1, file);
        offset += sizeof(chunk_header);
        switch (chunk_header.id) {
        case ID_FMT:
            fread(&chunk_fmt, sizeof(chunk_fmt), 1, file);
            offset += sizeof(chunk_fmt);

            /* If the format header is larger, skip the rest */
            if (chunk_header.sz > sizeof(chunk_fmt))
                fseek(file, chunk_header.sz - sizeof(chunk_fmt), SEEK_CUR);
                offset += chunk_header.sz - sizeof(chunk_fmt);
            break;
        case ID_DATA:
            /* Stop looking for chunks */
            more_chunks = 0;
            chunk_header.sz = le32toh(chunk_header.sz);
            break;
        default:
            /* Unknown chunk, skip bytes */
            fseek(file, chunk_header.sz, SEEK_CUR);
            LOGW("Unknown chunk:%u  chunk_header.sz=%d", chunk_header.id, chunk_header.sz);
            offset += chunk_header.sz;
        }
    } while (more_chunks);

    fclose(file);

    ret  = "offset:" + std::to_string(offset) + "\n";
    ret += "channels:" + std::to_string(chunk_fmt.num_channels) + "\n";
    ret += "sample_rate:" + std::to_string(chunk_fmt.sample_rate) + "\n";
    ret += "bit_format:" + std::to_string(chunk_fmt.bits_per_sample) + "\n";
    ret += "data_size:" + std::to_string(chunk_header.sz) + "\n";
    LOGD("parse %s:\n%s", filename, ret.c_str());
    return ret;
}

extern "C" JNIEXPORT jstring JNICALL
Java_com_fiill_audioplayernativelib_WaveParseLib_getParseString(
        JNIEnv* env,
        jobject /* this */, jstring jFileUrl) {
    const char *fileUrl = env->GetStringUTFChars(jFileUrl, 0);
    std::string ret = parse(fileUrl);
    return env->NewStringUTF(ret.c_str());
}

#if 0
static JNINativeMethod gMethods[] = {
        {"getParseString", "(Ljava/lang/String;)Ljava/lang/String;",
            (void *) Java_com_fiill_audioplayernativelib_WaveParseLib_getParseString},
        //add other functions
};

static int ap_register_natives(JNIEnv *env) {
    const char *className = "com/fiill/audioplayernativelib/WaveParseLib";
#ifdef BUILD_TO_ANDROID_OS
    return jniRegisterNativeMethods(env, className, gMethods, sizeof(gMethods) / sizeof(gMethods[0]));
#else
    return registerNativeMethods(env, className, gMethods, sizeof(gMethods) / sizeof(gMethods[0]));
#endif
}

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /*reserved*/){
    LOGD("-------------JNI_OnLoad --------\n");
    JNIEnv *env = NULL;
    if (vm->GetEnv((void **)&env, JNI_VERSION_1_4) != JNI_OK) {
        LOGE("Failed to GetEnv\n");
        return -1;
    }

    if (!ap_register_natives(env)){
        return -1;
    }
    return JNI_VERSION_1_4;
}
#endif
